﻿using Common.Exceptions;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Common
{
    /// <summary>
    /// 线程锁对象池
    /// <code><![CDATA[
    /// using(ThreadLockObjectPool.Lock("userid"))
    /// {
    ///     ....
    /// }
    /// ]]></code>
    /// </summary>
    public sealed class ThreadLockObjectPool
    {
        private static object SyncObj = new object();
        /// <summary>
        /// 默认池
        /// </summary>
        public static ThreadLockObjectPool Default { get; } = new ThreadLockObjectPool();
        private readonly List<ThreadLockObject> lockObjects = new List<ThreadLockObject>();
        /// <summary>
        /// 线程锁对象池
        /// </summary>
        private ThreadLockObjectPool()
        {
            inspectionThread = new Thread(Inspect);
            inspectionThread.IsBackground = true;
        }
        /// <summary>
        /// 执行锁定，请务必使用using块进行调用
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public ThreadLockObject Lock(string id)
        {
            lock (SyncObj)
            {
                var newObj = new ThreadLockObject(id);
                var bizLockObj = lockObjects.Find(t => t == newObj);
                if (bizLockObj == null)
                {
                    AddNewObject(newObj);
                    bizLockObj = newObj;
                }
                bizLockObj.Lock();
                return bizLockObj;
            }
        }
        private void AddNewObject(ThreadLockObject newObj)
        {
            lockObjects.Add(newObj);
            InspectionStart();
        }
        /// <summary>
        /// 执行锁定，请务必使用using块进行调用
        /// </summary>
        /// <param name="id"></param>
        /// <param name="lockImmediately">获取到对象后立即锁定</param>
        /// <returns></returns>
        public ThreadLockObject Lock(long id)
        {
            lock (SyncObj)
            {
                var newObj = new ThreadLockObject(id);
                var bizLockObj = lockObjects.Find(t => t == newObj);
                if (bizLockObj == null)
                {
                    AddNewObject(newObj);
                    bizLockObj = newObj;
                }
                bizLockObj.Lock();
                return bizLockObj;
            }
        }
        private readonly Thread inspectionThread;
        private bool inspectionRunning = false;
        /// <summary>
        /// 按需运行，当池内对象为空时不再运行洞察线程，直到有新的对象加入。
        /// </summary>
        private void InspectionStart()
        {
            if (inspectionRunning || inspectionThread.IsAlive)
            {
                return;
            }
            inspectionRunning = true;
            inspectionThread.Start();
        }
        private void Inspect()
        {
            while (lockObjects.Count > 0)
            {
                var expiredObjects = lockObjects.Where(x => DateTime.Now - x.LastAccessTime > TimeSpan.FromHours(1));
                if (expiredObjects != null && expiredObjects.Any())
                {
                    lock (SyncObj) //阻止其他线程获取锁对象，避免item冲突
                    {
                        foreach (var item in expiredObjects)
                        {
                            if (Monitor.TryEnter(item)) // 尝试给对象加锁，不等待其他锁释放，没获得锁就跳过
                            {
                                lockObjects.Remove(item);
                                Monitor.Exit(item);
                            }
                        }
                    }
                }
                Thread.Sleep(300_000); // 线程睡眠30秒
            }
            inspectionRunning = false;
        }
    }
    /// <summary>
    /// 线程锁对象
    /// </summary>    
    public class ThreadLockObject : IDisposable
    {
        private int threadId;
        /// <summary>
        /// 初始化线程锁对象
        /// </summary>
        /// <param name="resource"></param>
        public ThreadLockObject(string resource)
        {
            ExceptionHelper.ThrowIfNull(resource);
            this.Resource = resource;
            this.ResourceType = resource.GetType();
            this.LastAccessTime = DateTime.Now;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="resource"></param>
        public ThreadLockObject(long resource) : this(resource.ToString())
        {

        }
        /// <summary>
        /// 锁定的资源
        /// </summary>
        public object Resource { get; private set; }
        /// <summary>
        /// 资源类型
        /// </summary>
        public Type ResourceType { get; private set; }
        /// <summary>
        /// 最近访问时间
        /// </summary>
        public DateTime LastAccessTime { get; private set; }
        /// <summary>
        /// 是否被锁定
        /// </summary>
        internal bool Locking { get; set; }
        /// <inheritdoc/>
        public override bool Equals(object obj)
        {
            if (obj is ThreadLockObject tlo)
            {
                return tlo.Resource == this.Resource && tlo.ResourceType == this.ResourceType;
            }
            return false;
        }
        /// <inheritdoc/>
        public override int GetHashCode()
        {
            return Resource.GetHashCode();
        }
        internal ThreadLockObject Lock()
        {
            Monitor.Enter(this);
            Locking = true;
            this.threadId = Thread.CurrentThread.ManagedThreadId;
            this.LastAccessTime = DateTime.Now;
            return this;
        }

        internal void Unlock()
        {
            if (Locking)
            {
                Locking = false;
                threadId = -1;
                this.LastAccessTime = DateTime.Now;
                Monitor.Exit(this);
            }
        }
        /// <summary>
        /// 释放锁
        /// </summary>
        public void Dispose()
        {
            Unlock();
        }
        /// <inheritdoc/>
        public static bool operator ==(ThreadLockObject left, ThreadLockObject right)
        {
            if (object.Equals(left, null) && object.Equals(right, null))
            {
                return true;
            }
            if (object.Equals(left, null) || object.Equals(right, null))
            {
                return false;
            }
            return left.Equals(right);
        }
        /// <inheritdoc/>
        public static bool operator !=(ThreadLockObject left, ThreadLockObject right)
        {
            return !(left == right);
        }
        /// <inheritdoc/>
        public override string ToString()
        {
            return this.Locking ? $"{Resource} has been locked by thread-{threadId}" : $"{Resource} no lock";
        }
    }
}
